#include "SimulationScrollbars.h"

#include <algorithm>

#include <imgui.h>
#include <imgui_internal.h>

#include "StyleRepository.h"

namespace
{
    auto const ScrollbarThickness = 17.0f;
}

void _SimulationScrollbars::process(RealVector2D& worldCenter, RealRect const& worldRect, RealRect const& visibleWorldRect, RealRect const& viewRect)
{
    _isOneScrollbarHovered = false;

    processScrollbar(worldCenter, worldRect, visibleWorldRect, viewRect, Orientation::Horizontal);
    processScrollbar(worldCenter, worldRect, visibleWorldRect, viewRect, Orientation::Vertical);
}

_SimulationScrollbars::_SimulationScrollbars(bool onBackground)
    : _onBackground(onBackground)
{}

bool _SimulationScrollbars::isHoveredOrDragged() const
{
    return _isOneScrollbarHovered || isDragged();
}

bool _SimulationScrollbars::isDragged() const
{
    return _dragInfo.has_value();
}

void _SimulationScrollbars::processScrollbar(
    RealVector2D& worldCenter,
    RealRect const& worldRect,
    RealRect const& visibleWorldRect,
    RealRect const& viewRect,
    Orientation orientation)
{
    auto scrollbarRect = calcScrollbarRect(viewRect, orientation);
    auto sliderbarRect = calcSliderbarRect(worldRect, visibleWorldRect, viewRect, orientation);  // Relative to scrollbarRect

    // Trim slider bar position to be within scrollbar rect (considering padding)
    if (orientation == Orientation::Horizontal) {
        if (scrollbarRect.topLeft.x + sliderbarRect.bottomRight.x > scrollbarRect.bottomRight.x - scale(6.0f)) {
            sliderbarRect.bottomRight.x = scrollbarRect.bottomRight.x - scrollbarRect.topLeft.x - scale(6.0f);
        }
        if (scrollbarRect.topLeft.x + sliderbarRect.topLeft.x > scrollbarRect.bottomRight.x - scale(16.0f)) {
            sliderbarRect.topLeft.x = scrollbarRect.bottomRight.x - scrollbarRect.topLeft.x - scale(16.0f);
        }
    } else {
        if (scrollbarRect.topLeft.y + sliderbarRect.bottomRight.y > scrollbarRect.bottomRight.y - scale(6.0f)) {
            sliderbarRect.bottomRight.y = scrollbarRect.bottomRight.y - scrollbarRect.topLeft.y - scale(6.0f);
        }
        if (scrollbarRect.topLeft.y + sliderbarRect.topLeft.y > scrollbarRect.bottomRight.y - scale(16.0f)) {
            sliderbarRect.topLeft.y = scrollbarRect.bottomRight.y - scrollbarRect.topLeft.y - scale(16.0f);
        }
    }

    auto hovered = doesMouseCursorIntersectSliderBar(scrollbarRect, sliderbarRect);
    _isOneScrollbarHovered |= hovered;

    processEvents(worldCenter, worldRect, viewRect, hovered, orientation);

    auto drawList = _onBackground ? ImGui::GetBackgroundDrawList() : ImGui::GetWindowDrawList();
    drawList->AddRectFilled(
        ImVec2(scrollbarRect.topLeft.x, scrollbarRect.topLeft.y),
        ImVec2(scrollbarRect.bottomRight.x, scrollbarRect.bottomRight.y),
        ImColor::HSV(0, 0, 0, 0.5f * ImGui::GetStyle().Alpha),
        scale(8.0f));

    ImColor sliderColor = hovered || isDragged() ? ImColor(Const::SimulationSliderColor_Active) : ImColor(Const::SimulationSliderColor_Base);
    sliderColor.Value.w *= ImGui::GetStyle().Alpha;
    drawList->AddRectFilled(
        ImVec2(scrollbarRect.topLeft.x + sliderbarRect.topLeft.x, scrollbarRect.topLeft.y + sliderbarRect.topLeft.y),
        ImVec2(scrollbarRect.topLeft.x + sliderbarRect.bottomRight.x, scrollbarRect.topLeft.y + sliderbarRect.bottomRight.y),
        sliderColor,
        scale(5.0f));
}

RealRect _SimulationScrollbars::calcScrollbarRect(RealRect const& viewRect, Orientation orientation) const
{
    if (orientation == Orientation::Horizontal) {
        return RealRect{
            {viewRect.topLeft.x, viewRect.bottomRight.y - scale(ScrollbarThickness)},
            {viewRect.bottomRight.x - scale(1.0f + ScrollbarThickness), viewRect.bottomRight.y}};
    } else {
        return RealRect{
            {viewRect.bottomRight.x - scale(ScrollbarThickness), viewRect.topLeft.y},
            {viewRect.bottomRight.x, viewRect.bottomRight.y - scale(1.0f + ScrollbarThickness)}};
    }
}

void _SimulationScrollbars::processEvents(
    RealVector2D& worldCenter,
    RealRect const& worldRect,
    RealRect const& viewRect,
    bool mouseCursorIntersectSliderbar,
    Orientation orientation)
{
    if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
        if (mouseCursorIntersectSliderbar) {
            DragInfo dragInfo;
            if (orientation == Orientation::Horizontal) {
                dragInfo.worldCenter = worldCenter.x;
            } else {
                dragInfo.worldCenter = worldCenter.y;
            }
            dragInfo.orientation = orientation;
            _dragInfo = dragInfo;
            if (!_onBackground) {
                ImGui::SetActiveID(ImGui::GetID("SimulationScrollbar"), ImGui::GetCurrentWindow());
            }
        }
    }
    if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) && _dragInfo.has_value() && _dragInfo->orientation == orientation) {
        auto dragViewDelta = ImGui::GetMouseDragDelta();
        auto scrollbarSize = viewRect.bottomRight - viewRect.topLeft;
        auto worldSize = worldRect.bottomRight - worldRect.topLeft;
        auto dragWorldDelta = RealVector2D{dragViewDelta.x / scrollbarSize.x * worldSize.x, dragViewDelta.y / scrollbarSize.y * worldSize.y};
        auto newWorldCenter = worldCenter;
        if (Orientation::Horizontal == orientation) {
            newWorldCenter.x = _dragInfo->worldCenter + dragWorldDelta.x;
        } else {
            newWorldCenter.y = _dragInfo->worldCenter + dragWorldDelta.y;
        }
        worldCenter = newWorldCenter;
    }
    if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
        _dragInfo.reset();
    }
}

RealRect
_SimulationScrollbars::calcSliderbarRect(RealRect const& worldRect, RealRect const& visibleWorldRect, RealRect const& viewRect, Orientation orientation) const
{
    auto size2d = viewRect.bottomRight - viewRect.topLeft;
    auto worldSize = Orientation::Horizontal == orientation ? worldRect.bottomRight.x - worldRect.topLeft.x : worldRect.bottomRight.y - worldRect.topLeft.y;
    auto size = Orientation::Horizontal == orientation ? size2d.x : size2d.y;

    auto startWorldPos =
        Orientation::Horizontal == orientation ? visibleWorldRect.topLeft.x - worldRect.topLeft.x : visibleWorldRect.topLeft.y - worldRect.topLeft.y;
    auto endWorldPos =
        Orientation::Horizontal == orientation ? visibleWorldRect.bottomRight.x - worldRect.topLeft.x : visibleWorldRect.bottomRight.y - worldRect.topLeft.y;

    auto sliderBarStartPos = std::min(std::max(startWorldPos / worldSize * size, 0.0f), size);
    auto sliderBarEndPos = std::min(std::max(endWorldPos / worldSize * size, 0.0f), size);
    if (sliderBarEndPos < sliderBarStartPos) {
        sliderBarEndPos = sliderBarStartPos;
    }
    auto sliderBarPos =
        Orientation::Horizontal == orientation ? ImVec2{scale(4) + sliderBarStartPos, scale(4)} : ImVec2{scale(4), scale(4) + sliderBarStartPos};
    auto sliderBarSize = Orientation::Horizontal == orientation ? ImVec2{sliderBarEndPos - sliderBarStartPos - scale(8), scale(10)}
                                                                : ImVec2{scale(10), sliderBarEndPos - sliderBarStartPos - scale(8)};

    sliderBarSize = {std::max(scale(10.0f), sliderBarSize.x), std::max(scale(10.0f), sliderBarSize.y)};

    return {{sliderBarPos.x, sliderBarPos.y}, {sliderBarPos.x + sliderBarSize.x - scale(1), sliderBarPos.y + sliderBarSize.y - scale(1)}};
}

bool _SimulationScrollbars::doesMouseCursorIntersectSliderBar(RealRect const& scrollbarRect, RealRect const& sliderbarRect) const
{
    ImVec2 mousePositionAbsolute = ImGui::GetMousePos();
    auto extension = scale(3.0f);
    return mousePositionAbsolute.x >= scrollbarRect.topLeft.x + sliderbarRect.topLeft.x - extension
        && mousePositionAbsolute.x <= scrollbarRect.topLeft.x + sliderbarRect.bottomRight.x + extension
        && mousePositionAbsolute.y >= scrollbarRect.topLeft.y + sliderbarRect.topLeft.y - extension
        && mousePositionAbsolute.y <= scrollbarRect.topLeft.y + sliderbarRect.bottomRight.y + extension;
}
